Die ersten zwei Wochen in diesem Buch haben das Thema Eingabe und Ausgabe
(auch als E/A oder I/O für input/output bezeichnet) wiederholt am Rande gestreift.
Sie kennen <STDIN>
und print
für die Standardein- und -ausgabe und wissen, dass
man die Dateieingabe über die Befehlszeile, den <>
-Operator und die while
-Schleife
vornimmt, damit jede Zeile automatisch der Variablen $_
zugewiesen wird.
In dem heutigen Kapitel möchte ich Ihr Wissen über die Ein- und Ausgabe vertiefen, Ihnen noch ein bißchen mehr über die Argumentlisten für Skripts erzählen und Ihnen zeigen, wie Sie Daten und Optionen in Ihre Skripts einlesen. Heute erfahren Sie:
@ARGV
arbeitet
Getopt
verwendet, um Schalter zu verwalten
Schon zu Beginn dieses Buches, in Kapitel 3, »Weitere Skalare und Operatoren«, habe
ich Ihnen im Zusammenhang mit der Standardeingabe und -ausgabe ein wenig über
Datei-Handles erzählt. Damals habe ich Ihnen erklärt, dass STDIN
und STDOUT
eine
besondere Art von Datei-Handle darstellen, die sich auf Ein- und Ausgabestreams
beziehen, die - statt mit einer Datei - mit der Tastatur beziehungsweise dem Bildschirm
verbunden sind. Aber zum Glück läßt sich vieles von dem, was Sie bisher gelernt haben,
auch auf Datei-Handles übertragen, die sich auf das Dateisystem beziehen.
In diesem Abschnitt lernen Sie, wie Sie die tückischen Datei-Handles in den Griff
bekommen: wie man sie mit der open
-Funktion erzeugt, Daten aus ihnen ausliest oder
einliest beziehungsweise anhängt und wie man die Dateien wieder schließt, nachdem
sie ihren Dienst getan haben. Außerdem werden wir nebenbei wiederholen, was wir
bisher über Eingabe und Ausgabe gelernt haben.
Um Eingaben aus einer Quelle einzulesen oder Ausgaben an ein Ziel zu schreiben, benötigt man einen Datei-Handle. Ein Datei-Handle ist in der Regel mit einer bestimmten Datei auf der Festplatte verbunden, die zum Lesen oder Schreiben von Daten dient. Er kann sich aber auch auf eine Netzwerkverbindung (Socket), eine Pipe (eine Art Verbindung zwischen Standardeingabe und Standardausgabe, auf die wir noch am Tag 18, »Perl und das Betriebssystem«, eingehen) oder sogar bestimmte Hardware-Geräte beziehen. Das Konzept der Datei-Handles sorgt dafür, dass alle diese Operationen in gleicher Weise ausgeführt werden und Sie in gleicher Weise vorgehen können - egal woher die Daten kommen oder wohin sie gehen.
Perl stellt Ihnen drei Standard-Datei-Handles zur Verfügung, von denen Sie bereits
zwei kennen: STDIN
, STDOUT
und STDERR
. Die ersten zwei stehen für die
Standardeingabe und Standardausgabe (in der Regel Tastatur und Bildschirm). STDERR
ist die Standardfehlerausgabe und wird für Fehlermeldungen und andere Nachrichten
genutzt, die nicht Teil der eigentlichen Skriptausgabe sind. Normalerweise werden
STDERR
-Nachrichten wie bei STDOUT
auf dem Bildschirm ausgegeben. Nur
Programmen, die speziell Gebrauch von der Standardausgabe machen (zum Beispiel
Programme auf der anderen Seite von Unix-Pipes), wird hier ein Unterschied
auffallen.
Sie brauchen diese Datei-Handles weder zu öffnen noch zu initialisieren. Sie können sie so, wie sie sind, verwenden (wie bereits in den Lektionen der letzten Woche geschehen).
Um aus einer Datei aus- oder einzulesen, müssen Sie zuerst mit Hilfe der Funktion
open
für die betreffende Operation einen Datei-Handle erzeugen. Die open
-Funktion
öffnet eine Datei zum Einlesen (Eingabe) oder zum Schreiben (Ausgabe) von Daten
und verbindet diese Datei mit einem Datei-Handle (dessen Namen Sie frei vergeben
können). Beachten Sie, dass es sich beim Einlesen aus einer Datei und beim
Schreiben in eine Datei um getrennte Operationen handelt, die beide einen eigenen
Datei-Handle benötigen.
Die open
-Funktion übernimmt zwei Argumente: den Namen eines Datei-Handles und
die zu öffnende Datei (dazu gehört auch ein besonderer Code, der anzeigt, ob die
Datei zum Lesen oder Schreiben geöffnet wird). Hier einige Beispiele:
open(FILE, 'meinedatei');
open(CONFIG, '.scriptconfig');
open(LOG, '>/home/www/logfile');
Den Namen des Datei-Handles können Sie beliebig wählen. Die Konvention sieht jedoch vor, dass er aus Großbuchstaben besteht und Buchstaben, Zahlen oder Unterstriche enthält. Im Gegensatz zu den Variablen muss ein Datei-Handle mit einem Buchstaben beginnen.
Das zweite Argument ist der Name der Datei auf der Festplatte, die mit Ihrem Datei- Handle verbunden werden soll. Ein einfacher Dateiname ohne weitere Pfadangaben wird im aktuellen Verzeichnis gesucht (entweder das, in dem Ihr Skript ausgeführt wird, oder ein anderes Verzeichnis, sollten Sie es gewechselt haben). Alles weitere zum Navigieren von Verzeichnissen erfahren Sie in Kapitel 17, »Umgang mit Dateien und Verzeichnissen«.
Wollen Sie statt einfacher Dateiangaben Pfadnamen verwenden, sollten Sie Vorsicht
walten lassen - die Pfadnotation ist von Plattform zu Plattform unterschiedlich. Unter
Unix werden die einzelnen Pfade durch Schrägstriche (/
) getrennt, wie das letzte
unserer obigen Beispiele zeigt.
Auf Windows-Systemen läßt sich problemlos die Standard-DOS-Notation mit
Backslash (\
) zwischen den Verzeichnissen verwenden, solange Sie daran denken, den
ganzen Pfadnamen in einfache Anführungszeichen zu setzen. Zur Erinnerung: Ein
Backslash gehört zu den Sonderzeichen in Perl. Wenn Sie also die Anführungszeichen
fortlassen, erhalten Sie unter Umständen einen seltsamen Pfad, der keinen Bezug zur
Realität hat. Und wenn Sie den String in doppelte Anführungszeichen setzen, müssen
Sie vor den Backslash ein Escape-Zeichen (ebenfalls ein Backslash) setzen, um ein
korrektes Ergebnis zu erzielen:
open(FILE, 'c:\temp\nummern'); # korrekt
open(FILE, "c:\temp\nummern");
# ooh! enthält einen Tabulator und eine Neue Zeile (\t, \n)
open(FILE "c:\\temp\\nummern"); # korrekt
Da die meisten modernen Windows-Systeme auch Pfadnamen mit Schrägstrichen verstehen, sollten Sie vielleicht besser diese verwenden. Damit erhöhen Sie außerdem gleichzeitig die Portierbarkeit auf Unix-Systeme (falls Ihnen daran gelegen ist).
Beim Macintosh ist das Trennzeichen zwischen den Verzeichnissen ein Doppelpunkt, und der absolute Pfadname beginnt mit der Festplatte oder dem Laufwerksbezeichner (Festplatte, CD-ROM, Diskette und so weiter). Streben Sie die Portierung auf andere Systeme an, sollten Sie sich eine Notiz machen und Ihre Pfadnamen später konvertieren. Hier ein paar Beispiele für die Mac-Syntax:
open(FILE, "Meine Festplatte:Perl:config");
open(BOOKMARKS, "HD:System Folder:Preferences:Netscape:Bookmarks.html");
In jedem dieser Beispiele haben wir ein Datei-Handle geöffnet, um die Eingaben in
das Skript einzulesen. Dies entspricht dem Standard. Wollen Sie die Ausgabe in eine
Datei zurückschreiben, benötigen Sie dafür ebenfalls einen Datei-Handle, den Sie mit
open
öffnen - allerdings unter Verwendung eines besonderen Zeichens vor dem
Dateinamen:
open(OUT, ">output");
Das Zeichen > zeigt an, dass dieser Datei-Handle zum Schreiben von Daten geöffnet wird. Die gegebene Datei wird geöffnet und der aktuelle Inhalt, sofern vorhanden, gelöscht. (Um das Überschreiben bestehender Dateien zu vermeiden, können Sie in einem Test abfragen, ob eine Datei existiert, bevor Sie sie zum Schreiben von Daten öffnen - im Abschnitt »Datei-Tests« erfahren Sie mehr darüber.)
Wie verfahren Sie, wenn Sie Eingaben von einer Datei auslesen wollen, diese Daten dann bearbeiten und anschließend in dieselbe Datei zurückschreiben wollen? Dann müssen Sie zwei Datei-Handles öffnen: einen Handle zum Einlesen der Eingabe und später den zweiten Handle, um die Datei erneut zu öffnen und die Daten zurückzuschreiben. Lesen und Schreiben sind unterschiedliche Prozesse und bedürfen unterschiedlicher Datei-Handles.
Es sei angemerkt, dass es auch eine Syntax gibt, mit der man ein und dieselbe Datei für das Lesen und Schreiben öffnen kann:
"+>filename"
. Diesen Code können Sie verwenden, wenn Sie die Datei als eine Datenbank verstehen, die Sie nicht als Ganzes einlesen, sondern auf der Festplatte speichern und dann auf diese Datei zum Lesen und Schreiben zugreifen, wenn Sie Daten lesen oder ändern. In diesem Buch beschränke ich mich darauf, einfache Textdateien zu lesen und zu schreiben, so dass die Verwaltung der Daten mit Hilfe von zwei getrennten Datei-Handles weniger verwirrend und leichter ist: ein Handle, um die Daten in den Speicher einzulesen, und ein zweiter Handle, um die Daten wieder in die Datei zu schreiben.
Sie können eine Datei auch zum Anhängen öffnen - dabei bleibt der aktuelle Inhalt
der Datei erhalten, und alle Ausgaben über das Ausgabe-Datei-Handle werden an das
Ende der Datei angehängt. Um Daten anzuhängen, müssen Sie die Sonderzeichen >>
in Ihrem open
-Aufruf verwenden:
open(FILE, ">>logdatei");
Die Funktion open
wird fast immer zusammen mit einem logischen or
und einem
Aufruf von die
auf der anderen Seite aufgerufen:
open(FILE, "dieDatei") or die "dieDatei wurde nicht gefunden\n";
Der Aufruf von die
(»sterben«) ist nicht erforderlich, erfolgt aber so häufig in Perl, dass
die Kombination fast unzertrennlich wirkt. Wenn Sie die Kombination nicht
verwenden, ist es sehr wahrscheinlich, dass Sie irgend jemand, der Ihren Code sieht,
eines Tages darauf anspricht und fragt, warum Sie den Befehl fortgelassen haben.
»Öffne diese Datei oder stirb« ist die implizierte Drohung dieser Anweisung, und das ist
normalerweise genau das, was Sie auch wollen. Der open
-Befehl könnte fehlschlagen
- sei es, dass Sie eine Datei zum Lesen öffnen, die nicht existiert, oder dass Ihre
Festplatte sich seltsam verhält und die Datei nicht öffnen kann oder dass sonstige
unvorhersehbaren Ereignisse eintreten. Normalerweise wollen Sie nicht, dass Ihr
Skript einfach weiter ausgeführt wird, wenn etwas so absolut falsch läuft und das
Skript nichts zum Einlesen finden kann. Zum Glück liefert die open
-Funktion undef
zurück, wenn sie die Datei nicht öffnen kann (und 1 wenn es ihr gelungen ist), so dass
Sie dieses Ergebnis abfragen können und damit eine Entscheidungshilfe haben, was zu
tun ist.
Manchmal kann die Art, in der Sie auf den Fehler reagieren wollen, vom Skript
abhängen. In der Regel jedoch wollen Sie einfach nur das Skript mit einer
Fehlermeldung verlassen. Und genau dafür sorgt die die
-Funktion: Sie beendet
automatisch das gesamte Perl-Skript und gibt ihr Argument (eine String-Nachricht)
über den Datei-Handle STDERR
(normalerweise der Bildschirm) aus.
Wenn Sie ein Neue-Zeile-Zeichen an das Ende der Nachricht setzen, wird Perl diese
Nachricht beim Beenden des Skripts ausgeben. Wenn Sie das Neue-Zeile-Zeichen
weglassen, gibt Perl als zusätzliche Information die aktuelle Zeilennummer aus: at
script.pl line
nn
. Dabei ist script.pl
der Name Ihres Skripts und line
nn
die
Zeile, in der die
ausgelöst wurde. Diese Information kann beim späteren Debuggen
Ihres Skripts sehr nützlich sein.
Der Befehl die
kann aber noch mehr: Die spezielle Perl-Variable $!
enthält den
letzten Fehler des Betriebssystems (falls Ihr Betriebssystem einen solchen erzeugt hat).
Indem Sie die Variable $!
in den String für die
einbinden, kann Ihre Fehlermeldung
unter Umständen noch mehr Informationen liefern, als nur mitzuteilen, dass die Datei
nicht geöffnet werden konnte. So kann zum Beispiel die folgende Version von die
:
die "Datei konnte nicht geoeffnet werden: $!\n";
in Kombination mit der Variablen folgende Meldung ausgeben:
»Datei konnte nicht
geoeffnet werden:
Zugriff verweigert.
« Hier erfährt der Benutzer gleich, dass die
Datei nicht geöffnet wurde, weil die Zugriffsberechtigung für die Datei fehlte. Die
Verwendung von $!
ist immer dann zu empfehlen, wenn Sie die
als Antwort auf ein
Art von Systemfehler aufrufen.
Obwohl die
meist zusammen mit open
-Aufrufen verwendet wird, sollte man daraus
nicht schließen, dass die
nur in diesem Kontext sinnvoll einzusetzen ist. Sie können
die
(und seine weniger radikale Entsprechung warn
) überall dort in Ihrem Skript
einsetzen, wo Sie die Ausführung des Skripts abbrechen wollen (oder eine Warnung
ausgeben wollen). Weitere Informationen zu die
und warn
finden Sie in der perlfunc-
Manpage.
Angenommen Sie haben einen Datei-Handle, und er ist mit einer Datei verbunden,
die Sie zum Lesen geöffnet haben. Um Daten über den Datei-Handle einzulesen,
verwenden Sie den (Eingabe-)Operator <>
zusammen mit dem Namen des Datei-
Handles:
$line = <FILE>;
Kommt Ihnen sicherlich bekannt vor, oder? Bei STDIN
sind Sie genauso vorgegangen,
um eine über die Tastatur eingegebene Zeile einzulesen. Das ist das Tolle an den
Datei-Handles - Sie können genau die gleichen Prozeduren für das Lesen aus einer
Datei wie für das Lesen von der Tastatur oder von einer Netzwerkverbindung zu
einem Server verwenden. Perl macht da keinen Unterschied. Das Verfahren ist immer
dasselbe, und alles was Sie bisher über E/A gelernt haben, können Sie auch für die
Arbeit mit Dateien verwenden.
In einem skalaren Kontext liest der Eingabeoperator eine einzige Zeile bis zum Neue- Zeile-Zeichen ein:
$line = <STDIN>;
if (<FILE>) { print "weitere Eingaben..." };
Eine besondere Möglichkeit für die Verwendung des Eingabeoperators in einem
skalaren Kontext besteht darin, den Operator innerhalb des Tests einer while
-Schleife
zu verwenden. Damit erreichen Sie, dass bei jedem Schleifendurchlauf jeweils eine
Zeile eingelesen und diese Zeile dann der Variablen $_
zugewiesen wird. Die Schleife
hört erst auf, wenn das Ende der Eingabe erreicht ist:
while (<FILE>) {
# ... die Zeilen der Datei (in $_) verarbeiten
}
Die gleiche Notation ist Ihnen bereits häufiger begegnet, allerdings mit leeren
Eingabeoperatoren. Die leeren Eingabeoperatoren <>
stellen in Perl einen Sonderfall
dar. Wie Sie gelernt haben, verwendet man die leeren Eingabeoperatoren, um den
Inhalt von Dateien einzulesen, die in der Befehlszeile des Skripts angegeben werden.
In einem solchen Fall öffnet Perl die Dateien für Sie und sendet Ihnen deren Inhalt
Datei für Datei über den Datei-Handle STDIN
. Sie selbst brauchen nichts weiter zu tun.
Natürlich könnten Sie auch jede Datei selbst öffnen und einlesen, aber die <>
-
Operatoren in der while
-Schleife stellen eine wirklich praktische Hilfe dar, die den
Prozeß erheblich verkürzen.
In einem Listenkontext erreicht man mit den Eingabeoperatoren, dass die gesamte Eingabe auf einmal eingelesen wird, wobei jede Zeile der Eingabe einem Element der Liste zugeordnet wird. Seien Sie vorsichtig, wenn Sie den Eingabeoperator in einem Listenkontext verwenden, da er sich nicht immer so verhält, wie Sie es erwarten. Hierzu einige Beispiele:
@input = <FILE>; # liest die gesamte Datei nach @input ein;
$input = <FILE>; # liest die erste Zeile der Datei nach $input ein
($input) = <FILE>; # liest die erste Zeile der Datei nach $input ein und
# verwirft den Rest der Datei!
print <FILE>; # gibt den gesamten Inhalt von <FILE> auf dem Bildschirm
# aus
Um Ausgaben in einen Datei-Handle zu schreiben, verwendet man meist die
Funktionen print
oder printf
. (Es gibt zwar noch die write
-Funktion, doch wird
diese meist in Kombination mit Ausgabeformaten verwendet - ein Thema, das wir
erst in Kapitel 20, »Was noch bleibt«, ansprechen werden).
Die Funktion print
(wie auch printf
) geben ihre Daten standardmäßig an den Datei-
Handle STDOUT
aus. Um einen anderen Datei-Handle anzusprechen, zum Beispiel um
eine Zeile in eine Datei zu schreiben, müssen Sie erst den Datei-Handle zum
Schreiben öffnen:
open(FILE, ">$meinedatei") or
die "Kann die Datei $meinedatei nicht finden\n";
Und dann verwenden Sie print
mit dem Datei-Handles als Argument, um Daten in
dieser Datei abzulegen:
print FILE "$zeile\n";
Die Funktionen printf
und sprintf
sind in ihrer Funktionsweise ähnlich; denken Sie
nur daran, vor dem Formatierstring und den auszugebenden Werten den Datei-Handle
anzugeben, in den die Ausgabe erfolgen soll:
printf(FILE "%d Antworten wurden erfaßt\n", $total / $count);
Einen Punkt dürfen Sie bei der Verwendung von print
und printf
nicht vergessen:
Es steht kein Komma zwischen dem Datei-Handle und der Liste der Dinge, die
ausgegeben werden sollen. Dies ist einer der häufigsten Perl-Fehler (der allerdings
aufgefangen wird, wenn Sie die Perl-Warnungen eingeschaltet haben). Das Argument
des Datei-Handles ist vollkommen getrennt zu sehen von dem zweiten Argument, bei
dem es sich um eine Liste von Elementen handelt, die durch Kommata getrennt sind.
Bisher haben wir in diesem Buch nur Textdaten gelesen und geschrieben. Aber nicht alle Dateien, mit denen Sie in Perl konfrontiert werden, liegen im Textformat vor. Häufig hat man es mit binären Dateien zu tun. Wenn Sie Perl unter Unix oder auf einem Mac verwenden, macht das keinen großen Unterschied, denn Unix und MacPerl verarbeiten Text- und Binärdateien ohne Probleme. Arbeiten Sie hingegen unter Windows, erhalten Sie unleserliche Ergebnisse, wenn Sie versuchen, eine binäre Datei in einem normalen Perl-Skript zu verarbeiten.
Glücklicherweise kann man dem leicht abhelfen: Die Funktion binmode
übernimmt als
Argument einen einzelnen Datei-Handle und verarbeitet ihn (aus ihm lesen und in ihn
schreiben) in binärer Form:
open(FILE, "meineDatei.exe") or
die " Datei meineDatei konnte nicht geoeffnet werden: $!\n";
binmode FILE;
while (<FILE>) { # im binären Modus lesen...
Wenn Sie mit dem Lesen oder Schreiben der Daten über den Datei-Handle fertig sind,
sollte er geschlossen werden. Meist wird Ihnen diese Aufgabe abgenommen, denn
wenn die Ausführung Ihres Skripts beendet ist, schließt Perl alle Ihre Datei-Handles für
Sie. Und wenn Sie mit open
den gleichen Datei-Handle mehrmals hintereinander
öffnen (zum Beispiel um einen Datei-Handle, aus dem zuvor gelesen wurde, zum
Schreiben zu öffnen), trägt Perl dafür Sorge, dass der Datei-Handle automatisch
geschlossen wird, bevor Sie ihn erneut öffnen. Es gehört jedoch zum guten
Programmierstil, seine Datei-Handles zu schließen, nachdem sie ihren Dienst erfüllt
haben. Dann belegen Sie in Ihrem Skript auch keinen unnötigen Speicher mehr.
Einen Datei-Handle schließt man mit close
:
close FILE;
Mailboxen für E-Mails gehören zu den Formaten, die Perl wirklich gut beherrscht. Jede Nachricht folgt einem speziellen Format (auf der Basis des Protokolls RFC822), und alle Nachrichten zusammen werden in einer Mailbox mit ebenfalls speziellem Format gesammelt. Wenn Sie also eine Mailbox sichten und Nachrichten, die bestimmte Kriterien erfüllen, in irgendeiner Weise verarbeiten wollen, dann ist Perl Ihre Sprache. Wie man E-Mails filtert, werden wir uns in Kapitel 21, »Ein paar längere Beispiele«, noch genauer anschauen.
Das Beispiel in diesem Kapitel ist noch sehr einfach: Das Skript übernimmt eine
Mailbox als Argument aus der Befehlszeile, liest die Nachrichten einzeln ein, extrahiert
alle Zeilen, die mit subject beginnen (englisches Wort, mit dem die Betreff-Zeile der E-
Mail beginnt) und legt in dem gleichen Verzeichnis eine Datei namens Betreff
an, die
eine Liste der Betreffzeilen enthält. Existiert in dem Verzeichnis bereits eine Datei
dieses Namens, wird sie überschrieben (im Anschluß an diesen Abschnitt zeige ich
Ihnen, wie Sie testen können, ob eine gleichlautende Datei existiert, und dann eine
Warnung ausgeben lassen).
Listing 15.1 enthält den (sehr einfachen) Code.
Listing 15.1: Das Skript subject.pl
1: #!/usr/bin/perl -w
2: use strict;
3:
4: open(OUTFILE, ">Betreff") or
die " Datei Betreff konnte nicht geoeffnet werden: $!\n";
5:
6: while (<>) {
7: if (/^Subject:/) {
8: print OUTFILE $_;
9: }
10: }
11: close OUTFILE;
Ein kurzes Skript, werden Sie denken, das kaum als Beispiel taugt. Aber genau das ist
der springende Punkt: Zum Auslesen von Daten aus Dateien und zum Schreiben in
Dateien verwenden Sie die gleichen Techniken wie für die Standardeingabe und -
ausgabe. Auf zwei Dinge möchte ich Ihre Aufmerksamkeit lenken: Erstens, Zeile 4
öffnet die Datei Betreff zum Schreiben (beachten Sie dabei das Zeichen >
am Anfang
des Dateinamens) und zweitens, Zeile 8 übergibt unsere Ausgabe dem gleichen Datei-
Handle OUTFILE
und nicht der Standardausgabe.
Dieses Skript erzeugt zwar keine sichtbare Ausgabe, aber wenn Sie es auf einer Datei
mit abgespeicherten E-Mails ausführen und dann die Datei Betreff
betrachten, finden
Sie dort Zeilen wie die folgenden (mein Beispiel hier ist das Ergebnis einer Analyse
meiner »Business-Mail«):
Subject: FREE SOFTWARE TURN$ COMPUTER$ INTO CA$H MACHINE$!!
Subject: IBM 33.6 PCMCIA Modem $89.00
Subject: 48 MILLION Email Leads $195 + BONUSES
Subject: Re: E-ALERT: URGENT BUY RECOMMENDATION
Subject: Make $2,000 - $5,000 per week -NOT MLM
Subject: Email your AD to 57 MILLION People for ONLY $99
Subject: SHY?.....................................
Subject: You Could Earn $100 Every Time the Phone Rings!!
Das Öffnen von Dateien zum Einlesen oder Hineinschreiben ist gut und schön, wenn Sie die Dateien, mit denen Sie arbeiten, kennen - zum Beispiel wissen, dass sie alle vorhanden sind oder dass Sie nicht Gefahr laufen, wichtige Daten zu überschreiben. Manchmal jedoch wollen Sie in Perl die Eigenschaften einer Datei prüfen, bevor Sie sie öffnen, oder eine Datei in Abhängigkeit von ihren diversen Eigenschaften in der einen oder anderen Weise verarbeiten.
Perl kennt eine (ziemlich umfangreiche) Reihe von Tests für die verschiedenen
Eigenschaften von Dateien. Mit Hilfe dieser Tests läßt sich leicht feststellen, ob eine
Datei bereits existiert, ob Sie Daten enthält, zu welcher Art von Datei sie gehört oder
wie alt sie ist (»1996 war ein gutes Jahr für binäre Dateien, nicht wahr?«). Diese Tests
erinnern alle an Schalter (-e
, -R
, -o
und so weiter), sie sollten aber nicht mit diesen
verwechselt werden (die Ähnlichkeit ist eine Folge davon, dass sie alle ihren Ursprung
in der Unix-Shell-Skriptsprache haben).
Tabelle 15.1 gibt einen Überblick über einige der nützlicheren Dateitests. In der
perlfunc-Manpage sind unter dem Eintrag -X
alle Tests aufgeführt (allerdings sind
nicht alle Optionen für alle Plattformen verfügbar).
Jeder dieser Tests kann als Argument einen Dateinamen oder einen Datei-Handle
übernehmen; beides ist möglich (wenn Sie jedoch überprüfen wollen, ob es eine
bestimmte Datei gibt oder nicht, werden Sie mit Sicherheit den Dateinamen
übergeben, um den Test vor dem Aufruf von open
durchzuführen).
Fast alle Tests liefern entweder wahr (1
) oder falsch (»«) zurück. Nur -e
, -s
, -M
und -A
liefern andere Werte. Der Test -e
liefert undef
zurück, wenn die Datei nicht existiert,
-s
liefert die Anzahl der Bytes (Zeichen) in der Datei, und die Zeitoperatoren -M
und -A
liefern die Anzahl der Sekunden, die seit der letzten Änderung beziehungsweise dem
letzten Zugriff verstrichen sind.
Angenommen Sie wollten das Skript subject.pl dahingehend ändern, dass für den
Fall, dass eine Datei Betreff
bereits existiert, der Benutzer mit einer
Eingabeaufforderung selber entscheiden kann, ob die Datei überschrieben werden soll
oder nicht. Anstelle des bisherigen einfachen Aufrufs von open
könnten Sie einen Test
durchführen, um sicherzugehen, dass die Datei überhaupt existiert, und wenn ja, den
Benutzer entscheiden lassen, ob diese überschrieben werden soll oder nicht. Schauen
Sie sich dazu den folgenden Code an:
if (-e 'Betreff') {
print 'Datei existiert bereits. Überschreiben (J/N)? ';
chomp ($_ = <STDIN>);
while (/[^jn]/i) {
print 'J oder N, bitte: ';
chomp ($_ = <STDIN>);
}
if (/n/i) { die "Die Datei Betreff existiert bereits; Abbrechen.\n"; }
}
In diesem Beispiel sehen Sie eine andere Anwendungsmöglichkeit für die Funktion
die
- diesmal ohne die Funktion open
. Wenn der Benutzer die Frage zum
Überschreiben der Datei mit N
beantwortet, könnten Sie das Skript einfach verlassen.
Die die
-Funktion beendet das Skript ausdrücklich und gibt eine entsprechende
Nachricht aus.
Einen Aspekt bei der Ausführung von Perl-Skripten, den ich in den letzten Tagen
etwas vernachlässigt habe, betrifft den Umgang mit Befehlszeilenargumenten. Sie
haben schon ein wenig über Perls eigene Schalter (-e
, -w
und so weiter) erfahren, wie
aber gehen Sie vor, wenn Sie solche Schalter oder Argumente Ihren eigenen Skripten
übergeben wollen - wie kann man diese verarbeiten? Dieser Abschnitt behandelt im
besonderen folgende Themen: Skriptargumente im allgemeinen und den Einsatz von
Skriptschaltern.
Wenn Sie ein Perl-Skript nicht nur mit dem Namen des Skripts, sondern mit weiteren
Argumenten aufrufen, werden diese Argumente in einer besonderen globalen Liste,
der @ARGV-
Liste, gespeichert (für Mac-Droplets enthält @ARGV
die Namen der Dateien,
die auf das Droplet gezogen wurden). Sie können dieses Array genauso verarbeiten
wie jede andere Liste in Ihrem Perl-Skript. Sehen Sie im folgenden ein Codefragment,
das lediglich die Argumente, mit denen das Skript aufgerufen wurde, ausgibt, und
zwar ein Argument pro Zeile:
foreach my $arg (@ARGV) {
print "$arg\n";
}
Wenn Ihr Skript ein Konstrukt wie while
(<>
) verwendet, zieht Perl den Inhalt der
@ARGV
-Liste als Dateinamen zum Öffnen und Lesen heran (stehen keine Dateien in
@ARGV
, versucht Perl, von der Standardeingabe zu lesen). Mehrere Dateien werden
hintereinander geöffnet und gelesen, als ob es eine einzige große Datei wäre.
Wenn Sie mehr Kontrolle über den Inhalt der Dateien, die Sie in Ihr Skript einlesen,
haben wollen, können Sie die Namen der zu öffnenden und zu lesenden Dateien aus
der @ARGV
-Liste auslesen. Das Auswerten von @ARGV
bietet sich auch dann an, wenn
Sie nach bestimmten Argumenten suchen - zum Beispiel einer Konfigurationsdatei
und einer Datendatei. Wollen Sie hingegen den Inhalt einer beliebigen Anzahl von
Dateien bearbeiten, ist es praktischer, die Abkürzung <>
zu verwenden. Und wenn Sie
einen bestimmten Satz von Argumenten erwarten und kontrollieren wollen, wie diese
verarbeitet werden sollen, lesen Sie die Dateien von @ARGV
aus, und bearbeiten Sie sie
einzeln.
Im Gegensatz zu
argv
, wie es C und Unix kennen, enthält die@ARGV
-Liste von Perl nur die Argumente und nicht den Namen des Skripts selber ($ARGV[0]
enthält das erste Argument). Um den Namen des Skripts zu ermitteln, können Sie die spezielle Variable$0
verwenden.
Ein typischer Anwendungsbereich für die Skript-Befehlszeile ist die Übergabe von
Schaltern an ein Skript. Schalter sind Argumente, die mit einem Gedankenstrich
beginnen (-a
, -b
, -c
) und in der Regel dazu dienen, das Verhalten des Skripts zu
steuern. Manchmal bestehen Sie nur aus einem Buchstaben (-s
), manchmal sind sie
zu Gruppen zusammengefaßt (-abc
), und manchmal ist ihnen ein Wert oder
Argument zugeordnet (-o ausgabe.txt
).
Sie können ein Skript mit jedem beliebigen Schalter aufrufen. Die Schalter werden,
wie alle anderen Argumente auch, als Elemente im @ARGV
-Array aufgenommen. Wenn
Sie das Array @ARGV
mit <>
bearbeiten, müssen Sie die Schalter im Array erst
loswerden, bevor Sie irgendwelche Daten auslesen - sonst würde Perl davon
ausgehen, dass es sich bei -s
um einen Dateinamen handelt. Um alle Schalter, die
über das ganze @ARGV
-Array verstreut sind, zu bearbeiten und zu entfernen, könnten
Sie mühselig das Array durchgehen und versuchen herauszufinden, welche Elemente
davon Optionen und welche Optionen mit dazugehörigen Argumenten sind. Als
Endergebnis bliebe dann eine Liste der eigentlichen Dateinamen. Sie könnten sich
aber auch des Moduls Getopt
bedienen und sich damit die Arbeit abnehmen lassen.
Das Modul Getopt
, das als Teil der Standard-Modul-Bibliothek zusammen mit Perl
ausgeliefert wird, dient der Verwaltung der Skriptschalter. Eigentlich handelt es sich
um zwei Module: Getopt::Std
für die Bearbeitung von Schaltern, die aus einem
Buchstaben bestehen (-a
, -d
, -odatei
und so weiter), und Getopt::Long
, das fast
alle erdenklichen Optionen akzeptiert, einschließlich Optionen, die aus mehreren
Buchstaben bestehen (-sde
), oder Optionen im GNU-Stil mit doppeltem Bindestrich
(--help
, --size
und so weiter).
In diesem Abschnitt bespreche ich das Modul Getopt::Std
für die einfachen
Optionen. Wenn Sie für komplexere Optionen das Modul Getopt::Long
einsetzen
möchten, sollten Sie auf die Dokumentation zu diesem Modul zurückgreifen (Details
finden Sie in der perlmod-Manpage).
Für eine erfolgreiche Arbeit müssen Sie das Modul Getopt::Std
, wie jedes andere
Modul auch, in Ihr Skript importieren:
use Getopt::Std;
Durch den Import von Getopt::Std
erhalten Sie zwei Funktionen: getopt
und
getopts
. Diese Funktionen werden benötigt, um die Schalter aus Ihrem @ARGV
-Array
herauszuziehen und für jeden dieser Schalter in Ihrem Skript eine Skalarvariable zu
setzen.
Das Modul
Getopt
funktioniert in der Anwendungsversion von MacPerl nicht ordnungsgemäß, da es in MacPerl keine einfache Möglichkeit gibt, Befehlszeilenschalter einzulesen. Im Abschnitt »Skriptschalter auf dem Macintosh« finden Sie Hinweise, wie Sie dieses Problem in MacPerl umgehen.
Beginnen wir mit der Funktion getopts
, die einbuchstabige Schalter mit oder ohne
Werte definiert und bearbeitet. getopts
übernimmt ein einziges String-Argument, das
die Zeichen für die Schalter enthält, die von Ihrem Skript akzeptiert werden sollen.
Argumente, die Werte übernehmen, müssen von einem Doppelpunkt (:
) gefolgt
werden. Groß- und Kleinbuchstaben machen einen Unterschied und definieren
verschiedene Schalter. Schauen wir uns ein Beispiel an:
getopts('abc');
Das in diesem Beispiel verwendete Argument 'abc'
bearbeitet die Schalter -a
, -b
oder -c
in einer beliebigen Reihenfolge und ohne zugeordnete Werte. Die Schalter
können auch zusammengefaßt werden: -ab
oder -abc
lassen sich genauso verwenden
wie die einzelnen Schalter. Noch ein Beispiel:
getopts('ab:c');
Hier kann der Schalter -b
einen Wert übernehmen, der auf der Perl-Befehlszeile direkt
nach dem Schalter folgen muss:
% meinskript.pl -b 10
Das Leerzeichen hinter dem Schalter ist nicht erforderlich. -b10
läßt sich genauso
schreiben wie -b 10
. Sie können die Schalter sogar verschachteln, solange der Wert
nur nach dem korrekten Schalter erscheint:
% meinskript.pl -acb10 # OK
% meinskript.pl -abc10 # falsch, b und 10 gehören zusammen
Für jeden in getopts
definierten Schalter erzeugt getopts
einen Skalarvariablen-
Schalter mit dem Namen $opt_x
, wobei x
der Buchstabe des Schalters ist (in unserem
Beispiel würde getopts
drei Variablen $opt_a
, $opt_b
und $opt_c
erzeugen). Der
Anfangswert jeder Skalarvariablen ist 0
. Wenn der Schalter in den Argumenten zu
dem Skript enthalten ist (in @ARGV
steht), setzt getopts
den Wert der zugeordneten
Variable auf 1
. Erwartet ein Schalter einen Wert, weist getopts
den Wert aus @ARGV
der Skalarvariablen für die Option zu. Danach werden der Schalter und sein
dazugehöriger Wert aus dem Array @ARGV
gelöscht. Nachdem getopts
seine
Bearbeitung abgeschlossen hat, ist Ihr @ARGV
entweder leer oder enthält die restlichen
Argumente in Form von Dateinamen, die Sie dann mit den Datei-Handles oder mit <>
verarbeiten können.
Nachdem getopts
seine Aufgabe erledigt hat, steht Ihnen für jeden Schalter eine
Variable zur Verfügung, die entweder den Wert 0
hat (für einen unbenutzten Schalter),
1
(für einen benutzten Schalter) oder einen bestimmten Wert (für einen Schalter, der
einen Wert erfordert). Sie können diese Werte abfragen und Ihr Skript, je nachdem
mit welchem Schalter es aufgerufen wurde, verschiedene Operationen ausführen
lassen:
if ($opt_a) { # -a wurde verwendet
...
}
if ($opt_b) { # -b wurde verwendet
...
}
Wenn beispielsweise der Aufruf Ihres Skripts wie folgt aussieht:
% skript.pl -a
wird getopts
('abc'
) die Variable $opt_a
auf 1
setzen. Würde das Skript
folgendermaßen aufgerufen:
% skript.pl -a -R
wird $opt_a
auf 1
gesetzt und der Schalter -R
einfach gelöscht, ohne dass eine
Variable gesetzt wurde. Bei folgendem Aufruf des Skripts:
% skript.pl -ab10
und gleichzeitigem Aufruf von getopts
mit
getopts('ab:c');
wird $opt_a
auf 1
gesetzt und $opt_b
auf 10
.
Denken Sie daran, dass Perl bei Verwendung des Befehls use stric
t die plötzlich
auftauchenden Variablen $opt_
monieren wird. Das läßt sich jedoch vermeiden,
indem Sie diese Variablen im voraus mit use vars
wie folgt deklarieren:
use vars qw($opt_a $opt_b $opt_c);
Es gilt zu beachten, dass getopts
das Array @ARGV
der Reihe nach ausliest und die
Bearbeitung unterbricht, wenn es auf ein Element stößt, das nicht mit einem
Gedankenstrich (-
) beginnt oder das keinen Wert für eine vorausgehende Option
darstellt. Das bedeutet, dass Sie beim Aufruf eines Perl-Skripts zuerst die Optionen
und dann die anderen Argumente aufführen sollten. Andernfalls droht Ihnen, dass
Optionen unbearbeitet bleiben und Sie Fehler erhalten, weil versucht wird, Dateien zu
lesen, die gar keine Dateien sind. Sie können Ihr Skript natürlich auch so schreiben,
dass sichergestellt ist, dass @ARGV
leer ist, nachdem getopts
abgearbeitet ist, oder dass
die übriggebliebenen Argumente nicht mit einem Gedankenstrich beginnen.
Grundsätzlich sind die von getopts
definierten Schalter die einzigen Schalter, die Ihr
Skript akzeptiert. Wenn Sie ein Skript mit einem Schalter aufrufen, der nicht in dem
Argument zu getopts
definiert ist, gibt getopts
einen Unknown option
-Fehler aus
(»unbekannte Option«), löscht die Option aus @ARGV
und liefert falsch zurück. Dieses
Verhalten können Sie sich zunutze machen, um sicherzustellen, dass Ihr Skript korrekt
aufgerufen wird, und um im anderen Falle mit einer Meldung das Skript zu beenden.
Dazu muss der Aufruf von getopts
lediglich innerhalb eine if
-Anweisung erfolgen:
if (! getopts('ab:c')) {
die "Aufruf: meinskript -a -b:c datei\n";
}
Bedenken Sie auch, dass im Falle, dass getopts
die Bearbeitung Ihrer Schalter
aufgrund eines Fehlers mittendrin abbricht, alle Schaltervariablen, die zuvor gesetzt
wurden, ihre Werte beibehalten, auch die inkorrekten Werte. Je nachdem wie robust
Ihre Argumentenprüfung sein soll, können Sie diese Werte auch für den Fall
überprüfen, dass getopts
falsch zurückliefert (oder ganz abbricht).
Die Funktion getopt
gleicht der Funktion getopts
insofern, als beide ein
Stringargument übernehmen, in dem die Schalter definiert sind, jedem dieser
Argumente eine Variable $opt_
zuweisen und diese dann aus @ARGV
entfernen.
Zwischen getopt
und getopts
gibt es jedoch drei wesentliche Unterschiede:
getopt
ist ein String mit Schaltern, denen ein Wert zugeordnet
sein muss.
getopt
erübrigt sich die Definition von Argumenten ohne Werte. Jede
einbuchstabige Option ist erlaubt und für jede wird eine $opt_
-Variable erzeugt.
getopt
liefert keinen (nützlichen) Wert zurück und gibt auch keine Fehlermeldung
für unerwartete Optionen aus.
Angenommen Ihr Aufruf an getopt
lautet wie folgt:
getopt('abc');
Diese Funktion geht davon aus, dass Ihr Skript mit einer beliebigen Kombination der
drei Schalter -a
, -b
oder -c
aufgerufen wird, wobei jedem Schalter ein Wert
zugewiesen wird. Wird das Skript mit Schaltern aufgerufen, die keinen Wert haben,
sollten Sie von getopt
keine Warnung erwarten - statt dessen weist es der Variablen
für den Schalter freudig das nächste Element in @ARGV
zu, auch wenn das nächste
Element ein anderer Schalter oder ein Dateiname ist, der als Datei ausgelesen werden
sollte. Es obliegt Ihnen, herauszufinden, ob die Werte korrekt sind oder ob das Skript
mit dem falschen Satz an Argumenten aufgerufen wurde.
Im Grunde genommen liegt der Hauptunterschied zwischen getopt
und getopts
darin, dass Sie bei getopt
Ihre Optionen nicht deklarieren müssen, was jedoch die
Fehlerbehandlung wesentlich erschwert. Ich ziehe in den meisten Fällen getopts
vor,
da ich so unnötiges Wertetesten vermeiden kann.
Auf dem Mac gibt es keine Skript-Befehlszeile und deshalb können MacPerl-Skripts
auch keine Befehlszeilenschalter annehmen, wie das bei Unix oder bei Windows-
Skripten möglich ist (über Droplets können sie jedoch Dateinamenargumente
entgegennehmen). Das Problem läßt sich auf mehreren Wegen umgehen: So könnten
Sie zum Beispiel am Anfang Ihres Skripts zur Eingabe von Schalter auffordern und die
Eingabe in @ARGV
speichern (so dass Sie getopt
verwenden können, um die Schalter
innerhalb Ihres Skripts zu verarbeiten). Das folgende Codefragment zeigt hierzu ein
Beispiel:
print "Schalter eingeben: ";
chomp($in = <STDIN>);
@ARGV = split(' ', $in);
Eine etwas ausgefeiltere Version, die das MacPerl-Modul und ein Dialogfeld
verwendet, finden Sie als Teil von MacPerl FAQ unter http://www.perl.com/CPAN-
local/doc/FAQs/mac/MacPerlFAQ.html
:
if( $MacPerl::Version =~ /Application$/ ) {
# Ausführung als Anwendung
local( $cmdLine, @args );
$cmdLine = &MacPerl::Ask( "Befehlszeilenargumente eingeben:" );
require "shellwords.pl";
@args = &shellwords( $cmdLine );
unshift( @ARGV, @args );
}
Der wahre Mac-Benutzer umgeht die Befehlszeilenschalter, indem er MacPerl-Module verwendet, um eigene Dialoge zu erstellen, in denen die Schalter als echte Benutzerschnittstellenelemente implementiert werden. Ein paar einfache Dialoge werden wir in Kapitel 18 besprechen. Weitere Informationen zu den Dialogen finden Sie in der MacPerl-Dokumentation.
Wenn Sie die MPW-Version von MacPerl verwenden, können Sie das bisher Gesagte vergessen, denn hier haben Sie eine Befehlszeile. Also nutzen Sie sie!
Im folgenden sehen Sie ein einfaches Beispiel (Listing 15.2), das eine Datei auf unterschiedliche Arten bearbeitet. Wie die Datei konkret verarbeitet wird, hängt von den verwendeten Schaltern ab.
Listing 15.2: Das Skript schalter.pl
1: #!/usr/bin/perl -w
2: use strict;
3: use Getopt::Std;
4: use vars qw($opt_r $opt_l $opt_s $opt_n);
5:
6: if (! getopts('rlsn')) {
7: die "Aufruf: schalter.pl -rlsn\n";
8: }
9:
10: my @file = <>;
11:
12: if ($opt_s) {
13: @file = sort @file;
14: }
15:
16: if ($opt_n) {
17: @file = sort {$a <=> $b} @file;
18: }
19:
20: if ($opt_r) {
21: @file = reverse @file;
22: }
23:
24: my $i = 1;
25: foreach my $line (@file) {
26: if ($opt_l) {
27: print "$i: $line";
28: $i++;
29: } else {
30: print $line;
31: }
32: }
Dieses Skript verwendet nur einfache Schalter ohne Werte (beachten Sie den Aufruf
von getopts
in Zeile 6; es stehen keine Doppelpunkte nach den Optionen). Die
Schalter lauten -r
, um den Inhalt der Datei umzudrehen, -s
, um die Zeilen der Datei
alphabetisch zu sortieren, -n
, um die Zeilen numerisch zu sortieren, und -l
, um die
Zeilennummer auszugeben. Sie können die Optionen in der Befehlszeile kombinieren,
auch wenn manche Kombinationen nicht besonders sinnvoll sind (-sn
sortiert die
Datei und sortiert sie dann erneut numerisch).
In Zeile 4 werden die Variablen vorab deklariert, so dass Sie nicht plötzlich aus dem
Nichts auftauchen, wenn sie mit getopts
erzeugt werden (und Beschwerden durch use
strict
auslösen).
Der Test in den Zeilen 6 bis 8 stellt sicher, dass das Skript mit den richtigen Optionen
aufgerufen wird. Rutscht dabei eine undefinierte Option mit durch (zum Beispiel -a
oder -x
), dann bricht das Skript ab und gibt eine entsprechende Meldung aus.
Anschließend wird in verschiedenen if
-Anweisungen geprüft, ob die Variablen
$opt_r
, $opt_l
, $opt_s
und $opt_n
existieren, und dann werden je nach aufgerufener
Option auf der Befehlszeile verschiedene Operationen durchgeführt. Alle Argumente,
die keine Schalter sind, verbleiben nach dem Aufruf von getopts
in @ARGV
und werden
mit dem Operator <>
in Zeile 10 in das Skript eingelesen.
In dieser Lektion habe ich Ihnen die Grundlagen der Ein- und Ausgabe vermittelt und gezeigt, wie man Dateisysteme verwaltet. Das, was Sie hier gelernt haben, sollte sich auf alle Ihre Perl-Programme, die Dateien und Befehlszeilenargumente verwenden, übertragen lassen. In Kapitel 17 werden wir einen eingehenderen Blick auf das Dateisystem selbst werfen. In dem Vertiefungsabschnitt dieser Lektion möchte Ihnen aufzeigen, wo Sie weitere Informationen und Details zu fortgeschritteneren Aspekten der Ein- und Ausgabe und dem Umgang mit dem Dateisystem finden.
Alle vordefinierten Perl-Funktionen sind, wie ich bereits gesagt habe, in der perlfunc-
Manpage dokumentiert. Von Nutzen können aber auch die FAQs zu Dateien und
Formaten in der Hilfsdokumentation perlfaq
sein.
Hier noch einige weitere Kurzschreibweisen und Eigenheiten der open
-Funktion:
Sie können den Dateinamen beim Aufruf der open
-Funktion weglassen, wenn - und
nur in diesem Fall - einer Skalarvariablen, die den gleichen Namen wie das Datei-
Handle trägt, bereits der Name der zu öffnenden Datei zugewiesen wurde. Zum
Beispiel:
$FILE = "meinedatei.txt";
open(FILE) or die "Datei $FILE kann nicht geöffnet werden: $!\n";
Dies kann für Dateinamen, die geöffnet oder erneut geöffnet werden müssen, nützlich sein. Sie können die Variable zu Beginn Ihres Skripts setzen und dann den Dateinamen immer wieder verwenden.
Im Gegensatz zu dem, was ich Ihnen zu Beginn dieses Kapitels gesagt habe, können
Sie eine Datei auch gleichzeitig zum Lesen und Schreiben öffnen. Verwenden Sie
dazu das Sonderzeichen +>
vor den Dateinamen:
open(FILE, "+>diedatei") or die "Datei kann nicht geöffnet werden: $!\n";
Da dies jedoch oft nur Verwirrung stiftet, ziehe ich es vor, getrennte Datei-Handles zu verwenden und das Lesen und Schreiben als getrennte Operationen zu betrachten.
Dateinamen, die mit einem Pipe-Zeichen (|
) beginnen, fungieren als Befehl, und die
Ausgabe wird über die Befehls-Shell Ihres Systems an diesen Befehl geleitet.
Die Fülle an Möglichkeiten, die Ihnen open
bietet, finden Sie detailliert in der
Hilfsdokumentation perlfunc-Manpage beschrieben.
Tabelle 15.2 enthält weitere vordefinierte dateibezogene Funktionen, die ich in dieser Lektion noch nicht beschrieben habe.
Tabelle 15.2: Weitere E/A-Funktionen
Die bisher in diesem Kapitel beschriebenen Eingabe- und Ausgabetechniken umfassen die einfache, zeilenorientierte gepufferte Ein- und Ausgabe über Datei-Handles, der Standard-Ein-/-Ausgabe oder der Standardfehlerausgabe. Wenn Sie an den fortgeschrittenen Möglichkeiten der Ein- und Ausgabe interessiert sind, sollten Sie die Dokumentation der verschiedenen anderen E/A-Funktionen, die Perl für Sie bereithält, lesen. Eine Übersicht finden Sie in Tabelle 15.3.
Tabelle 15.3: Weitere E/A-Funktionen
Außerdem gibt es noch das Modul POSIX
, das weitere Möglichkeiten für
fortgeschrittene E/A-Operationen bietet (leider läßt sich dieses Modul nur unter Unix
einsetzen). Weitere Informationen zu POSIX
finden Sie in der perlmod-Manpage.
Perl bietet auch Unterstützung für Berkeley-Unix-DBM-Dateien (Datenbankdateien).
Diese Dateien sind in der Regel kleiner und schneller anzusprechen als reine textbasierte
Datenbanken. Weitere Informationen zu DBM finden Sie unter dem DB_File
-Modul,
der tie
-Funktion und den diversen Tie
-Modulen (Tie::Hash
, Tie::Scalar
und so
weiter).
CPAN enthält eine Reihe von Modulen für die Arbeit mit Datenbanken - sei es, dass Sie selbst Datenbanken erstellen wollen, sei es, dass Sie Schnittstellen und Treiber für den Zugriff auf kommerzielle Datenbanken wie Oracle und Sybase benötigen. Für letztgenannte seien die Pakete DBD (Datenbanktreiber) und DBI (Datenbankschnittstelle) der Perl Database Initiative wärmstens empfohlen.
Dateien und Verzeichnisse können mit sogenannten Zeitmarkierungen versehen
werden. Dabei handelt es sich um Angaben, wann die Datei erzeugt, geändert oder
zuletzt darauf zugegriffen wurde. Mit Hilfe von Dateitests (-M
für Änderungen, -A
für
Zugriff und -C
für Änderungen an den inode-Informationen1) können Sie die
Zeitmarkierungen überprüfen, mit Hilfe der stat
-Funktion erhalten Sie detailliertere
Informationen über die Zeitmarkierungen, und mit der utime
-Funktion läßt sich die
Zeitmarkierung einer Datei ändern. Das Verhalten der Tests und Funktionen kann
dabei von Plattform zu Plattform variieren.
Alle Zeitangaben erfolgen in Sekunden, gemessen ab einem bestimmten Zeitpunkt;
für Unix und Windows ist das der 1. Januar 1970, für den Macintosh der 1. Januar
1904. Zum Decodieren und Ändern von Zeitmarkierungen können auch die
Funktionen time
, gmtime
, localtime
und die Module Time::Local
nützlich sein.
In diesem Kapitel haben wir das, was Sie bisher über die Ein- und Ausgabe gelernt haben, vertieft und ergänzt. Dabei haben wir die Techniken, die beim Lesen aus der Standardeingabe und beim Schreiben in die Standardausgabe zur Anwendung kamen, auf das Einlesen und Schreiben von Dateien übertragen.
Zu Beginn standen Dateien und Datei-Handles im Zentrum unserer Betrachtung. Ich
habe Ihnen gezeigt, wie Sie die Funktion open
verwenden, um eine Datei zu öffnen
und ein Datei-Handle zu erzeugen, über das Sie aus dieser Datei lesen oder in die
Datei schreiben können. In Zusammenhang mit open
haben Sie die Funktion die
kennengelernt, die das Skript beendet und dabei eine Fehlernachricht an die
Standardfehlerausgabe schickt.
Im weiteren Verlauf der Lektion sprachen wir über Skript-Argumente und Schalter:
was passiert, wenn Sie ein Skript mit Argumenten aufrufen (sie werden in @ARGV
abgelegt), und wie Sie vorgehen, um diese Argumente zu bearbeiten. Enthalten diese
Argumente Schalter, bearbeiten Sie sie am besten mit dem Modul Getopt::Std
.
Damit können Sie Schalter für Ihr Skript definieren und bearbeiten und dann mit Hilfe
spezieller Variablen prüfen, ob diese Schalter überhaupt existieren.
Unter anderem haben Sie folgende Funktionen in diesem Kapitel kennengelernt:
open
- erzeugt Datei-Handles
die
- beendet das Skript mit einer Fehlernachricht
binmode
- setzt einen Datei-Handle in den Binärmodus
close
- schließt einen Datei-Handle
getopts
- Teil des Moduls Getopt
; deklariert und bearbeitet Argumente
getopt
- ebenfalls Teil des Moduls Getopt
; behandelt Argumente
Frage:
Ich versuche, eine Datei zum Schreiben zu öffnen, aber es wird immer wieder die
Funktion die
ausgelöst, und ich kann mir nicht erklären, warum. Das Verzeichnis
erlaubt den Lesezugriff, die Datei existiert noch nicht - es gibt keinen Grund,
warum hier etwas schieflaufen sollte.
Antwort:
Haben Sie daran gedacht, dass >
-Zeichen vor den Dateinamen zu setzen? Sie
brauchen dieses Zeichen, um Perl mitzuteilen, dass in die Datei geschrieben
werden soll. Andernfalls geht Perl davon aus, dass aus dieser Datei
ausgelesen wird, und wenn es diese Datei dann nicht findet, ist es auch nicht
in der Lage, sie zu öffnen.
Frage:
Ich möchte meine Datei mit einer Subroutine öffnen und dann den Datei-Handle
an andere Subroutinen weiterreichen. Aber wenn ich das versuche, funktioniert es
nicht. Warum?
Antwort:
Weil es so einfach nicht geht. Man kann zwar mit Typeglobs einige trickreiche
Kniffe anwenden, um Symbolnamen zwischen Subroutinen weiterzureichen,
doch ist dies ein Gebiet, das wieder seine ganz eigenen Probleme birgt. Am
besten übergibt man Datei-Handles, indem man das Modul FileHandle
nutzt,
den Datei-Handle als Objekt erzeugt und dieses Objekt dann zwischen den
Subroutinen weiterreicht.
Da wir jedoch noch nicht allzuviel über Objekte gelernt haben, könnten Sie (erst einmal) Ihr Datei-Handle global erzeugen und darauf in den Subroutinen Bezug nehmen. (Auf die Objekte werde ich in Kapitel 20 kurz zu sprechen kommen.)
Frage:
Ich versuche eine einfache textbasierte Datenbank in Perl zu lesen. Ich kenne das
Format der Datei und weiß, wie man es dekodieren muss, um etwas damit
anfangen zu können. Trotzdem ist die Eingabe, die ich erhalte, absolut
unverständlich. Wo liegt der Fehler?
Arbeiten Sie unter Windows? Liegt die Datenbank-Datei im binären Format vor? Verwenden Sie die Funktion
binmode
, um sicherzustellen, dass Perl Ihren Datei- Handle in binärem Format liest.
Frage:
Ich arbeite mit MacPerl. Jetzt habe ich eine Datei mit Zahlen von einem Unix-
System vor mir liegen, und wenn ich sie in mein Skript einlese, erhalte ich als
Ergebnis einen einzigen großen String anstatt eines Arrays von einzelnen Strings.
MacPerl ignoriert anscheinend die Neue-Zeile-Zeichen. Wie gehe ich weiter vor?
Antwort:
Das haben Sie ganz richtig diagnostiziert: MacPerl ignoriert die Neue-Zeile-
Zeichen. Unix- und Macintosh-Systeme handhaben Zeilenendezeichen
unterschiedlich. Unter Unix wird das Zeichen \n
(Zeilenvorschub, ASCII 10)
verwendet und unter Macintosh das Zeichen \r
(Wagenrücklauf, ASCII 13).
MacPerl ist nur zum Lesen von Macintosh-Dateien ausgelegt, so dass es ein
Wagenrücklaufzeichen anstelle eines Zeilenvorschubs erwartet. (Falls es Sie
interessiert, Windows/DOS verwendet beide).
Um dieses Problem zu umgehen, haben Sie zwei Möglichkeiten. Zum einen konvertieren FTP und die meisten Dateitransferprogramme heutzutage die Zeilenvorschubzeichen in Wagenrücklaufzeichen automatisch, wenn Sie eine Unix-Datei auf einen Mac verschieben. Und wenn nicht, gibt es viele Editoren, die das können. Nachdem Sie eine Unix-Datei in eine Mac-Datei konvertiert haben, dürfte das Problem nicht mehr auftauchen.
Antwort:
Die andere Lösung besteht darin, ganz oben in Ihrem Skript das
Eingabedatensatz-Trennzeichen von MacPerl in ein Zeilenvorschubzeichen zu
ändern:
$/ = "\n";
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.
STDIN
, STDOUT
und
STDERR
?
die
? Warum sollten man sie zusammen mit open
verwenden?
while
-Schleife und in
einem Listenkontext verhalten.
-e
-x
-f
-M
-Z
@ARGV
verwendet? Was enthält die Variable?
getopt
und getopts
?
getopts
:
getopts('xyz:');
getopts('x:y:z');
getopts('xXy');
getopts('xyz');
getopt('xyz');
gemischt
.
gemischt
. Sind die Extensionen unterschiedlich,
verlassen Sie das Skript mit einer Fehlermeldung. (HINWEIS: Verwenden Sie
reguläre Ausdrükke, um die Extensionen der Dateinamen zu ermitteln).
-o
. Ist die Option gesetzt, sollen auch
Dateien mit unterschiedlichen Extensionen vermischt werden (die Extension der
Ergebnisdatei können Sie frei wählen), und es wird keine Fehlermeldung
ausgegeben.
-u
, -s
, -r
und -c
. -u
liefert
den String in Großbuchstaben zurück, -s
entfernt Interpunktionszeichen und
Whitespace, -r
dreht den String um, und -c
zählt die Anzahl der Zeichen. Stellen
Sie sicher, dass die Optionen in verschiedenen Kombinationen verwendet werden
können, um unterschiedliche Effekte zu erzielen.
meinskript.pl -sz eingabedatei
$opt_z
liefert nicht
wahr zurück).
use strict;
use Getopt::Std;
use vars qw($opt_s $opt_z);
getopt('sz');
if ($opt_z) {
# ...
}
Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.
STDIN
, SDTOUT
und STDERR
beziehen sich auf die Standardeingabe, die
Standardausgabe und die Standardfehlerausgabe. Datei-Handles zu allen anderen
Dateien erzeugen Sie mit der Funktion open
.
die
-Funktion bricht das Skript behutsam ab und gibt dabei eine (hoffentlich)
hilfreiche Fehlernachricht aus. Meistens wird die Funktion zusammen mit open
verwendet, da man in dem Fall, dass eine Datei aus irgendwelchen Gründen nicht
geöffnet werden kann, das Skript üblicherweise nicht weiter ausführen möchte. Es
gehört daher zum guten Programmierstil, stets die Rückgabewerte von open
zu
überprüfen und die
aufzurufen, falls beim Aufruf von open
etwas schieflief.
<>
und den Namen des Datei-Handles. In einem skalaren
Kontext liest der Eingabeoperator immer nur eine Zeile. Innerhalb einer while
-
Schleife weist er die einzelnen Zeilen nacheinander der Variablen $_
zu. In einem
Listenkontext wird die gesamte Eingabe bis zum Ende der Datei eingelesen.
print
-
Funktion und den Namen des Datei-Handles. Beachten Sie, dass kein Komma
zwischen dem Datei-Handle und dem, was ausgegeben werden soll, steht.
-e
testet, ob die Datei existiert.
-x
testet, ob es sich bei der Datei um eine ausführbare Datei handelt
(normalerweise nur für Unix relevant).
-f
testet, ob es sich bei der Datei um eine einfache Datei (und nicht ein
Verzeichnis, einen Link oder etwas anderes) handelt.
-M
prüft das Bearbeitungsdatum der Datei.
-Z
testet, ob die Datei existiert und leer ist.
@ARGV
speichert alle Argumente und Schalter, mit denen
das Skript aufgerufen wurde.
getopt
definiert Schalter mit Werten, akzeptiert aber jede beliebige
Option. Die Funktion getopts
deklariert die möglichen Optionen für das Skript
und legt fest, ob diese Werte haben oder nicht. Ein weiterer Unterschied besteht
darin, dass getopts
den Wert falsch zurückliefert, wenn Fehler bei der
Bearbeitung der Befehlszeilenschalter aufgetreten sind. getopt
liefert keinen
praktischen Wert zurück.
getopts('xyz:')
definiert die Schalter -x
, -y
und -z
mit einem Wert
getopts('x:y:z')
definiert -x
und -y
mit Wert und -z
ohne Wert
getopts('xXy')
definiert -x
und -X
(beides sind separate Schalter) sowie -y
.
Keiner der Schalter hat einen Wert.
getopts('xyz')
definiert -x
, -y
und -z
alle ohne Werte.
getopt('xyz')
definiert -x
, -y
und -z
mit Werten sowie beliebige weitere
einbuchstabige Schalter.
#!/usr/bin/perl -w
use strict;
my ($file1, $file2) = @ARGV;
open(FILE1, $file1) or
die "Datei $file1 konnte nicht geoeffnet werden: $!\n";
open(FILE2, $file2) or
die "Datei $file2 konnte nicht geoeffnet werden: $!\n";
open(MERGE, ">gemischt") or
die "Die gemischte Datei konnte nicht geoeffnet werden: $!\n";
my $line1 = <FILE1>;
my $line2 = <FILE2>;
while (defined($line1) || defined($line2)) {
if (defined($line1)) {
print MERGE $line1;
$line1 = <FILE1>;
}
if (defined($line2)) {
print MERGE $line2;
$line2 = <FILE2>;
}
}
#!/usr/bin/perl -w
use strict;
my ($file1, $file2) = @ARGV;
my $ext;
if ($file1 =~ /\.(\w+)$/) {
$ext = $1;
if ($file2 !~ /\.$ext$/) {
die "Extensionen sind nicht identisch.\n";
}
}
open(FILE1, $file1) or
die "Datei $file1 konnte nicht geoeffnet werden: $!\n";
open(FILE2, $file2) or
die "Datei $file2 konnte nicht geoeffnet werden: $!\n";
open(MERGE, ">gemischt") or
die "Die gemischte Datei konnte nicht geoeffnet werden: $!\n";
my $line1 = <FILE1>;
my $line2 = <FILE2>;
while (defined($line1) || defined($line2)) {
if (defined($line1)) {
print MERGE $line1;
$line1 = <FILE1>;
}
if (defined($line2)) {
print MERGE $line2;
$line2 = <FILE2>;
}
}
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
use vars qw($opt_o);
getopts('o');
my ($file1, $file2) = @ARGV;
my $ext;
if ($file1 =~ /\.(\w+)$/) {
$ext = $1;
if ($file2 !~ /\.$ext$/) {
if (!$opt_o) {
die "Extensionen sind nicht identisch.\n";
}
}
}
open(FILE1, $file1) or
die "Datei $file1 konnte nicht geoeffnet werden: $!\n";
open(FILE2, $file2) or
die "Datei $file2 konnte nicht geoeffnet werden: $!\n";
open(MERGE, ">gemischt") or
die "Die gemischte Datei konnte nicht geoeffnet werden: $!\n";
my $line1 = <FILE1>;
my $line2 = <FILE2>;
while (defined($line1) || defined($line2)) {
if (defined($line1)) {
print MERGE $line1;
$line1 = <FILE1>;
}
if (defined($line2)) {
print MERGE $line2;
$line2 = <FILE2>;
}
}
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
use vars qw($opt_s $opt_r $opt_u $opt_c);
getopts('sruc');
my $str = $ARGV[0];
if ($opt_s) {
$str =~ s/[\s.,;:!?'"]//g;
}
if ($opt_r) {
$str = reverse $str;
}
if ($opt_u) {
$str = uc $str;
}
if ($opt_c) {
$str = length $str;
}
print "$str\n";
getopt
. Im Gegensatz zu getopts
geht diese Funktion davon aus, dass alle Schalter mit Werten versehen sind. Wenn
also das Skript mit dem Schalter -
sz
aufgerufen wird, geht getopt
davon aus,
dass Sie den Schalter -s
verwenden, der den Wert z
hat. Der Schalter -z
wird von
getopt
nie registriert. Verwenden Sie die Funktion getopts
, um sicherzustellen,
dass sowohl $opt_s
und $opt_z
gesetzt werden.
Die Option -C steht nur unter Unix zur Verfügung, wo jede Datei über eine inode-Datenstruktur verfügt, in der Informationen über die Datei verwaltet werden.